﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using SewerAPI.Core;
using SewerAPI.Util;

namespace SewerAPI.Security
{
    /// <summary>
    /// Standard encryption engine
    /// </summary>
    public sealed class StandardEncryptionEngine:IEncryptionEngine
    {
        #region Private Fields

        private byte[] privateKey = new byte[32] { 30, 125, 250, 1, 66, 98, 14, 32, 75, 55, 49, 99, 1, 45, 26, 18, 45, 125, 54, 22, 66, 83, 14, 96, 75, 63, 49, 99, 36, 65, 13, 18};
        private byte[] privateIV = new byte[16] { 5, 19, 37, 101, 113, 29, 13, 83, 97, 67, 23, 109, 2, 41, 59, 89 };

        #endregion

        #region Interface

        /// <summary>
        /// The name of this encryption class
        /// </summary>
        /// <returns></returns>
        public string GetEncryptionName()
        {
            return "StandardEncryption";
        }

        /// <summary>
        /// Decrypt a message
        /// </summary>
        /// <param name="encryptedmessage"></param>
        /// <returns></returns>
        public Message DecryptMessage(byte[] encryptedmessage)
        {
            try
            {
                if (encryptedmessage.Length > Constants.MAXENCRYPTEDCHANNELLENGTH)
                {
                    byte[] _message = new byte[encryptedmessage.Length - Constants.MAXENCRYPTEDCHANNELLENGTH];
                    Array.Copy(
                }
            }
            catch (Exception _ex)
            {
                throw _ex;
            }
        }

        /// <summary>
        /// Decrypt a channel
        /// </summary>
        /// <param name="encryptedmessage"></param>
        /// <returns></returns>
        public string DecryptChannel(byte[] encryptedmessage)
        {
            try
            {
                if (encryptedmessage.Length > Constants.MAXENCRYPTEDCHANNELLENGTH)
                {
                    byte[] _channel = new byte[Constants.MAXENCRYPTEDCHANNELLENGTH+1];
                    Array.Copy(encryptedmessage, 0, _channel, 0, Constants.MAXENCRYPTEDCHANNELLENGTH);
                    _channel = trim(_channel);
                    return decrypt(_channel);
                }
                else return string.Empty;
            }
            catch (Exception _ex)
            {
                throw _ex;
            }
        }

        /// <summary>
        /// Encrypt a message
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        public byte[] EncryptMessage(Message m)
        {
            try
            {
                byte[] _m = encrypt(m.Serialize(), this.privateKey,this.privateIV);
                byte[] _c = EncryptChannel(m);
                byte[] _result = new byte[_m.Length + _c.Length];
                _c.CopyTo(_result, 0);
                _m.CopyTo(_result, _c.Length);
                return _result;
            }
            catch (Exception _ex)
            {
                throw _ex;
            }
        }

        /// <summary>
        /// Encrypt a channel
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        public byte[] EncryptChannel(Message m)
        {
            try
            {
                byte[] _vector = new byte[Constants.MAXENCRYPTEDCHANNELLENGTH];
                byte[] _result = encrypt(m.Channel);
                return fill(_result, Constants.MAXENCRYPTEDCHANNELLENGTH);
            }
            catch (Exception _ex)
            {
                throw _ex;
            }
        }

        #endregion

        #region Private Encryption Methods

        /// <summary>
        /// Trim off the 0 values on the right side of the array
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        byte[] trim(byte[] source) 
        {
            int offset = source.Length;
            //Find the offset
            for (int i = source.Length - 1; i > 0; i--) 
            {
                if (source[i] != 0x0) 
                {
                    offset = i + 1;
                    break;
                }
            }
            //Copy the array
            byte[] trimmed = new byte[offset];
            Array.Copy(source, 0, trimmed, 0, offset);
            //Return it finally
            return trimmed;
        }

        /// <summary>
        /// Fill an array to the given length if the desired length
        /// is greater than the source array length. If no argument
        /// is provided, the array is filled with zero values.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="filledlength"></param>
        /// <returns></returns>
        byte[] fill(byte[] source, int filledlength = -1)
        {
            if (filledlength == -1)
            {
                for (int i = 0; i < source.Length; i++)
                {
                    source[i] = 0x0;
                }
                return source;
            }
            else if (source.Length < filledlength)
            {
                //Create a filled array with the new length
                byte[] filled = new byte[filledlength];
                filled = fill(filled);
                //Copy and return
                Array.Copy(source, 0, filled, 0, source.Length);
                return filled;
            }
            else return source;
        }

        /// <summary>
        /// Encrypt a string
        /// </summary>
        /// <param name="plainText"></param>
        /// <returns></returns>
        byte[] encrypt(string plainText)
        {
            return encrypt(plainText, privateKey, privateIV);
        }

        /// <summary>
        /// Encrypt a string
        /// </summary>
        /// <param name="text"></param>
        /// <param name="key"></param>
        /// <param name="iv"></param>
        /// <returns></returns>
        byte[] encrypt(string text, byte[] key, byte[] iv)
        {
            // Check arguments. 
            if (text == null || text.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (key == null || key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (iv == null || iv.Length <= 0)
                throw new ArgumentNullException("Key");
            byte[] _encrypted;
            // Create an Rijndael object 
            // with the specified key and IV. 
            using (Rijndael _rijndael = Rijndael.Create())
            {
                _rijndael.Key = key;
                _rijndael.IV = iv;
                // Create a decrytor to perform the stream transform.
                ICryptoTransform encryptor = _rijndael.CreateEncryptor(_rijndael.Key, _rijndael.IV);
                // Create the streams used for encryption. 
                using (MemoryStream _memstream = new MemoryStream())
                {
                    using (CryptoStream _cryptostream = new CryptoStream(_memstream, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter _writer = new StreamWriter(_cryptostream))
                        {
                            //Write all data to the stream.
                            _writer.Write(text);
                        }
                        _encrypted = _memstream.ToArray();
                    }
                }
            }
            // Return the encrypted bytes from the memory stream. 
            return _encrypted;
        }

        /// <summary>
        /// Decrypt a string
        /// </summary>
        /// <param name="cipherText"></param>
        /// <returns></returns>
        string decrypt(byte[] cipherText)
        {
            return decrypt(cipherText, privateKey, privateIV);
        }

        /// <summary>
        /// Decrypt a string
        /// </summary>
        /// <param name="encrypted"></param>
        /// <param name="key"></param>
        /// <param name="iv"></param>
        /// <returns></returns>
        string decrypt(byte[] encrypted, byte[] key, byte[] iv)
        {
            // Check arguments. 
            if (encrypted == null || encrypted.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (key == null || key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (iv == null || iv.Length <= 0)
                throw new ArgumentNullException("Key");
            // Declare the string used to hold 
            // the decrypted text. 
            string _text = null;
            // Create an Rijndael object 
            // with the specified key and IV. 
            using (Rijndael _rijndael = Rijndael.Create())
            {
                _rijndael.Key = key;
                _rijndael.IV = iv;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = _rijndael.CreateDecryptor(_rijndael.Key, _rijndael.IV);

                // Create the streams used for decryption. 
                using (MemoryStream _memstream = new MemoryStream(encrypted))
                {
                    using (CryptoStream _cryptostream = new CryptoStream(_memstream, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader _writer = new StreamReader(_cryptostream))
                        {
                            // Read the decrypted bytes from the decrypting stream 
                            // and place them in a string.
                            _text = _writer.ReadToEnd();
                        }
                    }
                }
            }
            return _text;
        }

        #endregion
    }
}
